<?php
/**
 * 安全增强中间件
 * 在API请求处理前进行安全检查
 */

declare(strict_types=1);

namespace app\middleware;

use app\service\SecurityEnhancementService;
use think\facade\Log;
use think\facade\Request;
use think\response\Json;

class SecurityEnhancement
{
    protected $securityService;
    
    public function __construct()
    {
        $this->securityService = new SecurityEnhancementService();
    }

    /**
     * 处理请求
     * @param \think\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, \Closure $next)
    {
        // 获取请求信息
        $method = $request->method();
        $endpoint = $request->pathinfo();
        $params = $request->param();
        $ip = $request->ip();
        
        try {
            // 1. 记录API访问日志
            $this->logApiAccess($request);
            
            // 2. 跳过安全检查的端点
            if ($this->isExemptEndpoint($endpoint)) {
                return $next($request);
            }
            
            // 3. API安全验证
            $apiSecurity = $this->securityService->validateApiSecurity($endpoint, $params);
            if (!$apiSecurity['allowed']) {
                return $this->securityErrorResponse($apiSecurity['reason'], $apiSecurity['threat_level']);
            }
            
            // 4. 登录相关端点的特殊处理
            if ($this->isLoginEndpoint($endpoint)) {
                $loginSecurity = $this->validateLoginSecurity($params, $ip);
                if (!$loginSecurity['allowed']) {
                    // 增加登录失败计数
                    if (isset($params['username'])) {
                        $this->securityService->incrementLoginAttempts($params['username'], $ip);
                    }
                    return $this->securityErrorResponse($loginSecurity['reason'], $loginSecurity['threat_level']);
                }
            }
            
            // 5. 需要认证的端点检查用户权限
            if ($this->requiresAuthentication($endpoint)) {
                $userId = $this->getCurrentUserId($request);
                if ($userId > 0) {
                    // 数据访问安全检查
                    $dataAccessSecurity = $this->validateDataAccessSecurity($userId, $endpoint, $params);
                    if (!$dataAccessSecurity['allowed']) {
                        return $this->securityErrorResponse($dataAccessSecurity['reason'], $dataAccessSecurity['threat_level']);
                    }
                }
            }
            
            // 6. 处理请求
            $response = $next($request);
            
            // 7. 登录成功后清除失败计数
            if ($this->isLoginEndpoint($endpoint) && $this->isSuccessfulResponse($response)) {
                if (isset($params['username'])) {
                    $this->securityService->clearLoginAttempts($params['username'], $ip);
                }
            }
            
            return $response;
            
        } catch (\Exception $e) {
            Log::error('安全中间件处理异常: ' . $e->getMessage(), [
                'endpoint' => $endpoint,
                'ip' => $ip,
                'trace' => $e->getTraceAsString()
            ]);
            
            return $this->securityErrorResponse('安全验证异常', 4);
        }
    }

    /**
     * 记录API访问日志
     */
    private function logApiAccess(\think\Request $request): void
    {
        try {
            $startTime = microtime(true);
            
            // 在请求结束后记录（简化版本，实际应该在响应中间件中处理）
            $logData = [
                'user_id' => $this->getCurrentUserId($request),
                'ip_address' => $request->ip(),
                'method' => $request->method(),
                'endpoint' => $request->pathinfo(),
                'user_agent' => $request->header('user-agent', ''),
                'request_data' => json_encode($request->param(), JSON_UNESCAPED_UNICODE),
                'response_code' => 200, // 默认值，实际应在响应后更新
                'response_time' => 0,   // 默认值，实际应在响应后更新
                'risk_score' => $this->calculateRiskScore($request),
                'create_time' => time()
            ];
            
            // 异步记录日志，避免影响响应性能
            $this->asyncLogApiAccess($logData);
            
        } catch (\Exception $e) {
            Log::error('API访问日志记录失败: ' . $e->getMessage());
        }
    }

    /**
     * 验证登录安全
     */
    private function validateLoginSecurity(array $params, string $ip): array
    {
        $username = $params['username'] ?? '';
        $password = $params['password'] ?? '';
        
        if (empty($username) || empty($password)) {
            return [
                'allowed' => false,
                'reason' => '用户名或密码不能为空',
                'threat_level' => 1
            ];
        }
        
        return $this->securityService->validateLoginSecurity($username, $password, $ip);
    }

    /**
     * 验证数据访问安全
     */
    private function validateDataAccessSecurity(int $userId, string $endpoint, array $params): array
    {
        // 解析端点获取模块和操作
        $module = $this->parseModuleFromEndpoint($endpoint);
        $dataId = $this->parseDataIdFromParams($params);
        
        if ($module && $dataId) {
            return $this->securityService->validateDataAccessSecurity($userId, $module, $dataId, [
                'endpoint' => $endpoint,
                'action' => $this->parseActionFromEndpoint($endpoint)
            ]);
        }
        
        return ['allowed' => true, 'reason' => '', 'threat_level' => 1];
    }

    /**
     * 检查是否为免安全检查的端点
     */
    private function isExemptEndpoint(string $endpoint): bool
    {
        $exemptEndpoints = [
            '/api/ping',
            '/api/health',
            '/api/version',
            '/api/captcha'
        ];
        
        foreach ($exemptEndpoints as $exempt) {
            if (strpos($endpoint, $exempt) === 0) {
                return true;
            }
        }
        
        return false;
    }

    /**
     * 检查是否为登录端点
     */
    private function isLoginEndpoint(string $endpoint): bool
    {
        $loginEndpoints = [
            '/api/auth/login',
            '/api/auth/register',
            '/api/login',
            '/api/register'
        ];
        
        return in_array($endpoint, $loginEndpoints);
    }

    /**
     * 检查是否需要认证
     */
    private function requiresAuthentication(string $endpoint): bool
    {
        // 公开端点，不需要认证
        $publicEndpoints = [
            '/api/auth/login',
            '/api/auth/register',
            '/api/captcha',
            '/api/ping',
            '/api/health'
        ];
        
        return !in_array($endpoint, $publicEndpoints);
    }

    /**
     * 获取当前用户ID
     */
    private function getCurrentUserId(\think\Request $request): int
    {
        // 从JWT token或session中获取用户ID
        $token = $request->header('authorization', '');
        if (strpos($token, 'Bearer ') === 0) {
            // 解析JWT token获取用户ID（简化版本）
            // 实际应该调用JWT服务验证token
            return 1; // 临时返回固定值
        }
        
        return 0;
    }

    /**
     * 检查响应是否成功
     */
    private function isSuccessfulResponse($response): bool
    {
        if ($response instanceof Json) {
            $data = $response->getData();
            return isset($data['code']) && $data['code'] == 200;
        }
        
        return false;
    }

    /**
     * 从端点解析模块名
     */
    private function parseModuleFromEndpoint(string $endpoint): ?string
    {
        // 解析如 /api/customer/list, /api/contract/detail 等
        if (preg_match('/\/api\/([^\/]+)/', $endpoint, $matches)) {
            return $matches[1];
        }
        
        return null;
    }

    /**
     * 从参数解析数据ID
     */
    private function parseDataIdFromParams(array $params): ?int
    {
        $idFields = ['id', 'data_id', 'record_id'];
        
        foreach ($idFields as $field) {
            if (isset($params[$field]) && is_numeric($params[$field])) {
                return (int)$params[$field];
            }
        }
        
        return null;
    }

    /**
     * 从端点解析操作类型
     */
    private function parseActionFromEndpoint(string $endpoint): string
    {
        if (strpos($endpoint, '/list') !== false) return 'list';
        if (strpos($endpoint, '/detail') !== false) return 'detail';
        if (strpos($endpoint, '/create') !== false) return 'create';
        if (strpos($endpoint, '/update') !== false) return 'update';
        if (strpos($endpoint, '/delete') !== false) return 'delete';
        
        return 'unknown';
    }

    /**
     * 计算请求风险评分
     */
    private function calculateRiskScore(\think\Request $request): int
    {
        $score = 0;
        
        // 基于多个因素计算风险评分
        $endpoint = $request->pathinfo();
        $method = $request->method();
        $params = $request->param();
        
        // 高风险端点
        $highRiskEndpoints = ['/admin', '/system', '/config'];
        foreach ($highRiskEndpoints as $risk) {
            if (strpos($endpoint, $risk) !== false) {
                $score += 3;
                break;
            }
        }
        
        // 敏感操作
        if (in_array($method, ['DELETE', 'PUT'])) {
            $score += 2;
        }
        
        // 大量参数
        if (count($params) > 20) {
            $score += 1;
        }
        
        // 参数中包含SQL关键字
        $sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'UNION'];
        foreach ($params as $value) {
            if (is_string($value)) {
                foreach ($sqlKeywords as $keyword) {
                    if (stripos($value, $keyword) !== false) {
                        $score += 5;
                        break 2;
                    }
                }
            }
        }
        
        return min($score, 10); // 最高评分10
    }

    /**
     * 异步记录API访问日志
     */
    private function asyncLogApiAccess(array $logData): void
    {
        // TODO: 实现异步队列处理
        // 这里使用简化的同步记录
        try {
            \think\facade\Db::name('api_access_log')->insert($logData);
        } catch (\Exception $e) {
            Log::error('API访问日志记录失败: ' . $e->getMessage());
        }
    }

    /**
     * 返回安全错误响应
     */
    private function securityErrorResponse(string $reason, int $threatLevel): Json
    {
        switch ($threatLevel) {
            case 4:
                $httpCode = 403; // 严重威胁 - 禁止访问
                break;
            case 3:
                $httpCode = 429; // 高威胁 - 请求过多
                break;
            case 2:
                $httpCode = 401; // 中威胁 - 未授权
                break;
            default:
                $httpCode = 400; // 低威胁 - 请求错误
                break;
        }
        
        return json([
            'code' => $httpCode,
            'message' => '安全验证失败',
            'data' => [
                'reason' => $reason,
                'threat_level' => $threatLevel
            ]
        ], $httpCode);
    }
}